package erfgame.core.world.terrain.file;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;


import erfgame.core.Point3D;
import erfgame.core.dao.ObjectDAO;
import erfgame.core.memory.MemoryPool;
import erfgame.core.memory.Pointer;
import erfgame.core.world.terrain.TerrainUtils;

public class CompressingFileBlockTerrainDAO implements ObjectDAO<Point3D, Pointer<byte[][][]>> {
	
	private static final String EXTENSION = ".ter";
	
	private int blockWidth;
	private int blockHeight;
	private int blockDepth;
	
	private File baseDirectory;
	
	private MemoryPool<byte[][][]> memoryPool;
	
	public CompressingFileBlockTerrainDAO( 
			File baseDirectory, 
			int blockWidth, 
			int blockHeight, 
			int blockDepth,
			MemoryPool<byte[][][]> memoryPool
	) {
		this.blockWidth = blockWidth;
		this.blockHeight = blockHeight;
		this.blockDepth = blockDepth;
		
		this.baseDirectory = baseDirectory;
		this.memoryPool = memoryPool;
	}
	
	private final File getFile( int x, int y, int z ) {
		StringBuffer fileName = new StringBuffer( 30 );
		fileName.append( x );
		fileName.append( 'x' );
		fileName.append( y );
		fileName.append( 'x' );
		fileName.append( z );
		fileName.append( EXTENSION );
		File file = new File( this.baseDirectory, fileName.toString() );
		return file;
	}
	
	public boolean containsObject(Point3D key) {
		return getFile( key.x, key.y, key.z ).exists();
	}

	public boolean removeObject(Point3D key) {
		File file = getFile( key.x, key.y, key.z );
		if( file.exists() ) {
			file.delete();
			return true;
		} else {
			return false;
		}
	}

	public Pointer<byte[][][]> retrieveObject(Point3D key) {
		Pointer<byte[][][]> result = memoryPool.claim(false).createPointer();
		loadTerrain( key.x, key.y, key.z, result.getValue() );
		return result;
	}

	public void storeObject(Point3D key, Pointer<byte[][][]> object) {
		storeTerrain( key.x, key.y, key.z, object.getValue() );
	}

	public void loadTerrain(int x, int y, int z, byte[][][] terrain) {
		FileInputStream fis = null;
		File file = getFile( x, y, z );
		try {
			fis = new FileInputStream( file );
			BufferedInputStream bis = new BufferedInputStream( fis );
			TerrainUtils.uncompress( 
					bis, 
					terrain, 
					this.blockWidth,
					this.blockHeight,
					this.blockDepth
			);
		} catch( IOException ex ) {
			throw new RuntimeException( "unable to load "+file.getPath(), ex );
		} finally {
			if( fis != null ) {
				try {
					fis.close();
				} catch( IOException ex ) {
					ex.printStackTrace();
				}
			}
		}
	}

	public void storeTerrain(int x, int y, int z, byte[][][] terrain) {
		File file = getFile( x, y, z );
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream( file );
			BufferedOutputStream bos = new BufferedOutputStream( fos );
			TerrainUtils.compress( 
					bos, 
					terrain, 
					this.blockWidth, 
					this.blockHeight, 
					this.blockDepth
			);
			bos.flush();
		} catch( IOException ex ) {
			throw new RuntimeException( "unable to write to "+file.getPath(), ex );
		} finally {
			if( fos != null ) {
				try {
					fos.close();
				} catch( IOException ex ) {
					ex.printStackTrace();
				}
			}
		}
	}
}
